//TrigonometricFunc module
//Processing time: max. 15T

module TrigonometricFunc(
	iRST_N,
	iCLK,
	iStartProcess,
	iFunc,
	oBusy,
	oError,
	iData,
	oData
);

localparam PI			= 32'h40490fdb;	// Pi
localparam _PI			= 32'hc0490fdb;	// -Pi
localparam PI2			= 32'h40c90fdb;	// 2*Pi
localparam PI_2		= 32'h3FC90FDB;	// Pi/2
localparam PI3_2		= 32'h4096CBE4;	// 3*Pi/2
localparam C8192_PI	= 32'h4522F983;	// 8192 / Pi
localparam C4096		= 32'h45800000;	// 4096
localparam C1			= 32'h3f800000;	// +1
localparam C_1			= 32'hbf800000;	// -1

input [31:0] iData;
output reg [31:0] oData;

input iRST_N,iStartProcess,iCLK;
output oBusy;
assign oBusy = process_started;
input [1:0] iFunc; //00: Sin; 01: Cos; 10: Acos; 11: Error
output reg oError;

reg prev_iStartProcess;
reg process_started;
reg [31:0] data_in, temp, addr1, delta;
reg [4:0] state;
reg [5:0] timer;
reg neg;
reg[13:0] pre_addr;
reg[1:0] func;

always@(posedge iCLK or negedge iRST_N)
begin
	if(!iRST_N)	begin
		process_started <= 1'b0;
		prev_iStartProcess <= 1'b1;
		state <= 5'd0;
		timer <= 6'd0;
		oError <= 1'b0;
		oData <= 0;
	end
	else begin
		prev_iStartProcess <= iStartProcess;		
		if({prev_iStartProcess,iStartProcess} == 2'b01 && !process_started) begin
			func <= iFunc;
			data_in <= iData;			
			timer <= 6'd0;
			process_started <= 1'b1;
			oError <= 0;
			
			if(iFunc[1] == 1'b0) begin
				if(iFunc[0] == 1'b0) neg <= iData[31];
				else neg <= 0;				
				Mult1A <= {1'b0, iData[30:0]};
				Mult1B <= C8192_PI;
				state <= 5'd1;
			end
			else if(iFunc[0] == 1'b0) begin
				if(iData[30:0] > C1) state <= 5'd0;	// Error
				else begin
					Mult1A <= {1'b0, iData[30:0]};
					Mult1B <= C4096;
					state <= 5'd5;
				end
			end
			else state <= 5'd0;	// Error			
		end
		
		else if(process_started) begin
			case (state)
				//--------------------------------------------
				//Error state
				0: begin
					oError <= 1'b1;
					oData <= 32'h7FFFFFFF;
					
					if(timer == 'd3) process_started <= 1'b0;
					else timer <= timer + 1'b1;
				end
				//--------------------------------------------
				//Sin and Cos state				
				1: begin										
					if(timer == 6'd4) begin			//wait till mult finished
						timer <= 6'd0;
						state <= 5'd2;
						FloatToIntA <= Mult1Res;
					end
					else timer <= timer + 1'b1;
				end
				2: begin										
					if(timer == 6'd5) begin			//wait till convert finished
						timer <= 6'd0;
						state <= 5'd3;
						pre_addr <= (FloatToIntRes + (func[0] == 1'b1 ? 4096 : 0)) % 16384;						
					end
					else timer <= timer + 1'b1;
				end
				3: begin
					if(pre_addr < 4096) SinTableROM_address <= pre_addr[11:0];
					else if(pre_addr == 4096) begin
						oData <= C1;
						process_started <= 1'b0;
					end
					else if(pre_addr < 8192) SinTableROM_address <= 8192 - pre_addr[12:0];
					else begin
						neg <= ~neg;
						if(pre_addr < 12288) SinTableROM_address <= pre_addr - 8192;
						else if(pre_addr == 12288) begin
							oData <= C_1;
							process_started <= 1'b0;
						end
						else SinTableROM_address <= 16384 - pre_addr;
					end
					state <= 5'd4;
				end
				4: begin									
					if(timer == 6'd1) begin		//wait till read finished
						timer <= 6'd0;	
						oData[31] <= neg;
						oData[30:0] <= SinTableROM_q[30:0];	//set the output data							
						process_started <= 1'b0;
					end
					else timer <= timer + 1'b1;
				end
				
				//--------------------------------------------
				//ACos calculation
				//data_in is in the interval of -1->1						
				5: begin										
					if(timer == 'd5) begin			//wait till x4096 finished
						timer <= 'd0;
						state <= 'd6;
						FloatToIntA <= Mult1Res;	// convert to int to get LUT address
						temp <= Mult1Res;
					end
					else timer <= timer + 1'b1;
				end
				6: begin										
					if(timer == 'd6) begin			//wait till mem address conversion finished
						timer <= 'd0;
						state <= 'd7;
						AcosTableROM_address <= FloatToIntRes[12:0];	// fetch data
						IntToFP1A <= FloatToIntRes[12:0];		// convert back to float to calculate error
					end
					else timer <= timer + 1'b1;
				end
				7: begin
					if(timer == 'd6) begin
						timer <= 'd0;
						Add1A <= temp;					// input * 4096 - LUT address
						Add1B <= {1'b1, IntToFP1Res[30:0]};
						addr1 <= AcosTableROM_q;	// store LUT data
						state <= 'd8;
					end
					else timer <= timer + 1'b1;
				end
				8: begin
					if(timer == 'd7) begin
						timer <= 'd0;
						if(Add1Res == 0) begin		// if no error, no interpolation needed
							temp <= addr1;
							state <= 'd13;
						end
						else begin
							delta <= Add1Res;			// address error
							if(Add1Res[31]) AcosTableROM_address <= AcosTableROM_address - 1'd1;	// fetching neighbour data
							else AcosTableROM_address <= AcosTableROM_address + 1'd1;
							state <= 'd9;
						end
					end
					else timer <= timer + 1'b1;
				end
				9:begin
					if(timer == 'd1) begin	// calculate *LUT - *(LUT+1)
						timer <= 'd0;
						Add1A <= delta[31] ? addr1 : AcosTableROM_q;
						Add1B <= delta[31] ? {1'b1, AcosTableROM_q[30:0]} : {1'b1, addr1[30:0]};
						state <= 'd10;
					end
					else timer <= timer + 1'b1;
				end				
				10: begin			
					if(timer == 'd7) begin
						timer <= 'd0;
						Mult1A <= delta;		// address error * difference
						Mult1B <= Add1Res;
						state <= 'd11;
					end
					else timer <= timer + 1'b1;						
				end
				11: begin
					if(timer == 'd5) begin
						timer <= 'd0;
						Add1A <= Mult1Res;
						Add1B <= addr1;
						state <= 'd12;
					end
					else timer <= timer + 1'b1;
				end
				12: begin
					if(timer == 'd7) begin
						timer <= 'd0;
						temp <= Add1Res;
						state <= 'd13;
					end
					else timer <= timer + 1'b1;
				end
				13: begin										
					if(data_in[31] == 1'b0) begin
						oData <= temp;
						process_started <= 1'b0;
					end
					else begin
						Add1A <= PI;
						Add1B <= {1'b1, temp[30:0]};
						state <= 'd14;
					end	
				end
				14: begin									
					if(timer == 6'd7) begin			//wait till add finished
						oData <= Add1Res;
						process_started <= 1'b0;
					end
					else timer <= timer + 1'b1;	
				end		
			endcase
		end				
	end
end

//6T
reg [31:0] IntToFP1A;
wire [31:0] IntToFP1Res;
IntToSinglePrecFloatConvert i2fp(
	.clock(~iCLK),
	.dataa(IntToFP1A),
	.result(IntToFP1Res)
);

//7T
reg [31:0] Add1A;
reg [31:0] Add1B;
wire [31:0] Add1Res;
SinglePrecFloatAdd u1(
	.clock(~iCLK),
	.dataa(Add1A),
	.datab(Add1B),
	.result(Add1Res)
);

//6T
reg [31:0] FloatToIntA;
wire [31:0] FloatToIntRes;
SinglePrecFloatToInt u5(
	.clock(~iCLK),
	.dataa(FloatToIntA),
	.result(FloatToIntRes)
);

reg [11:0] SinTableROM_address;
wire [31:0] SinTableROM_q;
SinTableROM u6(
	.address(SinTableROM_address),
	.clock(~iCLK),
	.q(SinTableROM_q)
);

reg [12:0] AcosTableROM_address;
wire [31:0] AcosTableROM_q;
assign AcosTableROM_q = (AcosTableROM_address == 4096) ? 0 : acos_q;
wire[31:0] acos_q;
AcosTableROM u8(
	.address(AcosTableROM_address[11:0]),
	.clock(~iCLK),
	.q(acos_q)
);

//5T
reg[31:0] Mult1A, Mult1B;
wire[31:0] Mult1Res;
SinglePrecFloatMult m1(
	.dataa(Mult1A),
	.datab(Mult1B),
	.result(Mult1Res),
	.clock(~iCLK)
);

endmodule
